package com.github.cyf.interceptor.log;

import com.github.cyf.util.ExecutorUtil;
import com.github.cyf.util.PrintUtil;
import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import java.util.Properties;

/**
 *
 * @author chenyifan
 * @create 2024-04-11 9:53
 */
@Intercepts({
        // 拦截 Executor 类的查询、修改方法
        @Signature(
                type = Executor.class,
                method = "query",
                args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}
        ),
        @Signature(
                type = Executor.class,
                method = "query",
                args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}
        ),
        @Signature(
                type = Executor.class,
                method = "update",
                args = {MappedStatement.class, Object.class}
        )
})
public class SqlLogInterceptor implements Interceptor {

    /**
     * 是否显示 sql 执行时间
     */
    public static final String IS_PRINT_SQL_TIME = "isPrintSqlTime";

    private boolean isPrintSqlTime = false;

    private static PrintUtil printUtil = new PrintUtil(SqlLogInterceptor.class);

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        printUtil.printInterceptorBegin();

        Object[] args = invocation.getArgs();
        MappedStatement ms = (MappedStatement) args[0];
        // 获取传入的参数对象，类型为 MapperMethod.ParamMap，实质是继承了 HashMap
        // 其中 key 为参数名称，value 为参数值
        Object parameter = invocation.getArgs()[1];
        // 获取 boundSql，里面存储了原始 SQL 语句及参数信息
        // getBoundSql：该方法用于生成 BoundSql 对象，里面包含了 configuration、sql、参数映射等信息
        BoundSql boundSql = args.length == 6 ? (BoundSql) args[5] : ms.getBoundSql(parameter);

        // 执行被代理方法，获取 sql 执行时长
        long startTime = System.currentTimeMillis();
        Object result = invocation.proceed();
        long endTime = System.currentTimeMillis();

        // 获取 mapperId，接口全限名+方法名
        String msId = ms.getId();
        String realSql = ExecutorUtil.getRealSql(boundSql);
        printUtil.printSql(msId, realSql);

        // 打印 sql 执行时间
        if (isPrintSqlTime) {
            printUtil.printSqlTime(msId, startTime, endTime);
        }
        printUtil.printInterceptorEnd();
        return result;
    }

    /**
     * 通过该方法决定要返回的对象是目标对象还是对应的代理
     * 不要想的太复杂，一般就两种情况：
     * <p>
     * 1. return target;  直接返回目标对象，相当于当前 Interceptor 没起作用，不会调用上面的 intercept() 方法
     * 2. return Plugin.wrap(target, this);  返回代理对象，会调用上面的 intercept() 方法
     *
     * @param target 目标对象
     * @return       目标对象或者代理对象
     */
    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    /**
     * 用于在 Configuration 初始化当前 Interceptor 时设置一些参数
     *
     * @param properties
     */
    @Override
    public void setProperties(Properties properties) {
        // 判断是否显示 sql 执行时间
        if (properties != null) {
            if (Boolean.TRUE.toString().equals(properties.getProperty(IS_PRINT_SQL_TIME))) {
                isPrintSqlTime = true;
            }
        }
    }

}